﻿
#include "Ray.h"

#include <Riccsson/Xna/Framework/BoundingBox.h>
#include <Riccsson/Xna/Framework/BoundingSphere.h>
#include <Riccsson/Xna/Framework/BoundingFrustum.h>
#include <Riccsson/Xna/Framework/Plane.h>

using namespace Riccsson::System;
using namespace Riccsson::Xna::Framework;

Ray::Ray(Vector3 position, Vector3 direction)
{
	this->Position = position;
	this->Direction = direction;
}
bool Ray::Equals(Riccsson::System::object* obj)
{
	return (instanceof(obj, Ray)) ? this->Equals((Ray*)obj) : false;
}
bool Ray::Equals(Ray* other)
{
	return this->Position.Equals(&other->Position) && this->Direction.Equals(&other->Direction);
}
int Ray::GetHashCode()
{
	//return Position.GetHashCode() ^ Direction.GetHashCode();
	throw;
}
Riccsson::System::Nullable<float> Ray::Intersects(BoundingBox& box)
{
	const float Epsilon = 1e-6f;

	Riccsson::System::Nullable<float> tMin = null, tMax = null;

	if (Riccsson::System::Math::Abs(Direction.X) < Epsilon)
	{
		if (Position.X < box.Min.X || Position.X > box.Max.X)
			return null;
	}
	else
	{
		tMin = (box.Min.X - Position.X) / Direction.X;
		tMax = (box.Max.X - Position.X) / Direction.X;

		if (tMin > tMax)
		{
			var temp = tMin;
			tMin = tMax;
			tMax = temp;
		}
	}

	if (Riccsson::System::Math::Abs(Direction.Y) < Epsilon)
	{
		if (Position.Y < box.Min.Y || Position.Y > box.Max.Y)
			return null;
	}
	else
	{
		float tMinY = (box.Min.Y - Position.Y) / Direction.Y;
		float tMaxY = (box.Max.Y - Position.Y) / Direction.Y;

		if (tMinY > tMaxY)
		{
			float temp = tMinY;
			tMinY = tMaxY;
			tMaxY = temp;
		}

		if ((tMin.HasValue && tMin > tMaxY) || (tMax.HasValue && tMinY > tMax))
			return null;

		if (!tMin.HasValue || tMinY > tMin) tMin = tMinY;
		if (!tMax.HasValue || tMaxY < tMax) tMax = tMaxY;
	}

	if (Riccsson::System::Math::Abs(Direction.Z) < Epsilon)
	{
		if (Position.Z < box.Min.Z || Position.Z > box.Max.Z)
			return null;
	}
	else
	{
		var tMinZ = (box.Min.Z - Position.Z) / Direction.Z;
		var tMaxZ = (box.Max.Z - Position.Z) / Direction.Z;

		if (tMinZ > tMaxZ)
		{
			var temp = tMinZ;
			tMinZ = tMaxZ;
			tMaxZ = temp;
		}

		if ((tMin.HasValue && tMin > tMaxZ) || (tMax.HasValue && tMinZ > tMax))
			return null;

		if (!tMin.HasValue || tMinZ > tMin) tMin = tMinZ;
		if (!tMax.HasValue || tMaxZ < tMax) tMax = tMaxZ;
	}

	// having a positive tMin and a negative tMax means the ray is inside the box
	// we expect the intesection distance to be 0 in that case
	if ((tMin.HasValue && tMin < 0.0f) && tMax > 0.0f)
		return 0.0f;

	// a negative tMin means that the intersection point is behind the ray's origin
	// we discard these as not hitting the AABB
	if (tMin < 0.0f)
		return null;

	return tMin;
}
void Ray::Intersects(Plane ref plane, Riccsson::System::Nullable<float> out result)
{
	float den = Vector3::Dot(Direction, plane.Normal);
	if (Riccsson::System::Math::Abs(den) < 0.00001f)
	{
		result = null;
		return;
	}

	result = (-plane.D - Vector3::Dot(plane.Normal, Position)) / den;

	if (result < 0.0f)
	{
		if (result < -0.00001f)
		{
			result = null;
			return;
		}

		result = 0.0f;
	}
}
void Ray::Intersects(BoundingBox ref box, Riccsson::System::Nullable<float> out result)
{
	result = Intersects(box);
}
Riccsson::System::Nullable<float> Ray::Intersects(BoundingSphere& sphere)
{
	Riccsson::System::Nullable<float> result;
	Intersects(/*ref*/ sphere, /*out*/ result);
	return result;
}
Riccsson::System::Nullable<float> Ray::Intersects(BoundingFrustum& frustum)
{
	// ???
	//if (frustum == null)
	//{
	//    throw new ArgumentNullException("frustum");
	//}

	return frustum.Intersects(*this);

}
Riccsson::System::Nullable<float> Ray::Intersects(Plane& plane)
{
	Riccsson::System::Nullable<float> result;
	Intersects(/*ref*/ plane, /*out*/ result);
	return result;
}
void Ray::Intersects(BoundingSphere ref sphere, Riccsson::System::Nullable<float> out result)
{
	// Find the vector between where the ray starts the the sphere's centre
	Vector3 difference = sphere.Center - this->Position;

	float differenceLengthSquared = difference.LengthSquared();
	float sphereRadiusSquared = sphere.Radius * sphere.Radius;

	float distanceAlongRay;

	// If the distance between the ray start and the sphere's centre is less than
	// the radius of the sphere, it means we've intersected. N.B. checking the LengthSquared is faster.
	if (differenceLengthSquared < sphereRadiusSquared)
	{
		result = 0.0f;
		return;
	}

	Vector3::Dot(/*ref*/ this->Direction, /*ref*/ difference, /*out*/ distanceAlongRay);
	// If the ray is pointing away from the sphere then we don't ever intersect
	if (distanceAlongRay < 0)
	{
		result = null;
		return;
	}

	// Next we kinda use Pythagoras to check if we are within the bounds of the sphere
	// if x = radius of sphere
	// if y = distance between ray position and sphere centre
	// if z = the distance we've travelled along the ray
	// if x^2 + z^2 - y^2 < 0, we do not intersect
	float dist = sphereRadiusSquared + distanceAlongRay * distanceAlongRay - differenceLengthSquared;

	if(dist < 0)
		result = null;
	else
		result = (distanceAlongRay - *Riccsson::System::Nullable<float>((float)Riccsson::System::Math::Sqrt(dist)));
}
GC_PTR<Riccsson::System::string> Ray::ToString()
{
	//return string.Format("{{Position:{0} Direction:{1}}}", Position.ToString(), Direction.ToString());
	throw;
}